[Chapter Twenty][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Twenty
- 20.7 - Simulating Keystrokes
- 20.7.1 - Stuffing Characters in the Type
Ahead Buffer
- 20.7.2 - Using the 80x86 Trace Flag to
Simulate IN AL, 60H Instructions
20.7 Simulating Keystrokes
At one point or another you may want to write a program that passes
keystrokes on to another application. For example, you might want to write
a keyboard macro TSR that lets you capture certain keys on the keyboard
and send a sequence of keys through to some underlying application. Perhaps
you'll want to program an entire string of characters on a normally unused
keyboard sequence (e.g., ctrl-up or ctrl-down). In any case, your program
will use some technique to pass characters to a foreground application.
There are three well-known techniques for doing this: store the scan/ASCII
code directly in the keyboard buffer, use the 80x86 trace
flag to simulate in al, 60h
instructions, or program the on-board
8042 microcontroller to transmit the scan code for you. The next three sections
describe these techniques in detail.
20.7.1 Stuffing Characters in the Type Ahead
Buffer
Perhaps the easiest way to insert keystrokes into an application is
to insert them directly into the system's type ahead buffer. Most modern
BIOSes provide an int 16h function to do this (see "The
Keyboard BIOS Interface" on page 1168). Even if your system does
not provide this function, it is easy to write your own code to insert data
in the system type ahead buffer; or you can copy the code from the int 16h
handler provided earlier in this chapter.
The nice thing about this approach is that you can deal directly with ASCII
characters (at least, for those key sequences that are ASCII). You do not
have to worry about sending shift up and down codes around the scan code
for tn "A" so you can get an upper case "A", you need
only insert 1E41h into the buffer. In fact, most programs ignore the scan
code, so you can simply insert 0041h into the buffer and almost any application
will accept the funny scan code of zero.
The major drawback to the buffer insertion technique is that many (popular)
applications bypass DOS and BIOS when reading the keyboard. Such programs
go directly to the keyboard's port (60h) to read their data. As such, shoving
scan/ASCII codes into the type ahead buffer will have no effect. Ideally,
you would like to stuff a scan code directly into the keyboard controller
chip and have it return that scan code as though someone actually pressed
that key. Unfortunately, there is no universally compatible way to do this.
However, there are some close approximations, keep reading...
20.7.2 Using the 80x86 Trace Flag to Simulate
IN AL, 60H Instructions
One way to deal with applications that access the keyboard hardware
directly is to simulate the 80x86 instruction set. For example, suppose
we were able to take control of the int 9 interrupt service routine and
execute each instruction under our control. We could choose to let all instructions
except the in
instruction execute normally. Upon encountering
an in
instruction (that the keyboard ISR uses to read the keyboard
data), we check to see if it is accessing port 60h. If so, we simply load
the al
register with the desired scan code rather than actually
execute the in
instruction. It is also important to check for
the out
instruction, since the keyboard ISR will want to send
and EOI signal to the 8259A PIC after reading the keyboard data, we can
simply ignore out
instructions that write to port 20h.
The only difficult part is telling the 80x86 to pass control to our routine
when encountering certain instructions (like in
and out
)
and to execute other instructions normally. While this is not directly possible
in real mode, there is a close approximation we can make. The 80x86 CPUs
provide a trace flag that generates an exception after the execution of
each instruction. Normally, debuggers use the trace flag to single step
through a program. However, by writing our own exception handler for the
trace exception, we can gain control of the machine between the execution
of every instruction. Then, we can look at the opcode of the next instruction
to execute. If it is not an in
or out
instruction,
we can simply return and execute the instruction normally. If it is an in
or out
instruction, we can determine the I/O address and decide
whether to simulate or execute the instruction.
In addition to the in
and out
instructions, we
will need to simulate any int
instructions we find as well.
The reason is because the int
instruction pushes the flags
on the stack and then clears the trace bit in the flags register. This means
that the interrupt service routine associated with that int
instruction would execute normally and we would miss any in
or out
instructions appearing therein. However, it is easy
to simulate the int
instruction, leaving the trace flag enabled,
so we will add int
to our list of instructions to interpret.
The only problem with this approach is that it is slow. Although the trace
trap routine will only execute a few instructions on each call, it does
so for every instruction in the int 9 interrupt service routine. As a result,
during simulation, the interrupt service routine will run 10 to 20 times
slower than the real code would. This generally isn't a problem because
most keyboard interrupt service routines are very short. However, you might
encounter an application that has a large internal int 9 ISR and this method
would noticeably slow the program. However, for most applications this technique
works just fine and no one will notice any performance loss while they are
typing away (slowly) at the keyboard.
The following assembly code provides a short example of a trace exception
handler that simulates keystrokes in this fashion:
.xlist
include stdlib.a
includelib stdlib.lib
.list
cseg segment para public 'code'
assume ds:nothing
byp textequ <byte ptr>
; ScanCode must be in the Code segment.
ScanCode byte 0
;****************************************************************************
;
; KbdSim- Passes the scan code in AL through the keyboard controller
; using the trace flag. The way this works is to turn on the
; trace bit in the flags register. Each instruction then causes a trace
; trap. The (installed) trace handler then looks at each instruction to
; handle IN, OUT, INT, and other special instructions. Upon encountering
; an IN AL, 60 (or equivalent) this code simulates the instruction and
; returns the specified scan code rather than actually executing the IN
; instruction. Other instructions need special treatment as well. See
; the code for details. This code is pretty good at simulating the hardware,
; but it runs fairly slow and has a few compatibility problems.
KbdSim proc near
pushf
push es
push ax
push bx
xor bx, bx ;Point es at int vector tbl
mov es, bx ; (to simulate INT 9).
cli ;No interrupts for now.
mov cs:ScanCode, al ;Save output scan code.
push es:[1*4] ;Save current INT 1 vector
push es:2[1*4] ; so we can restore it later.
; Point the INT 1 vector at our INT 1 handler:
mov word ptr es:[1*4], offset MyInt1
mov word ptr es:[1*4 + 2], cs
; Turn on the trace trap (bit 8 of flags register):
pushf
pop ax
or ah, 1
push ax
popf
; Simulate an INT 9 instruction. Note: cannot actually execute INT 9 here
; since INT instructions turn off the trace operation.
pushf
call dword ptr es:[9*4]
; Turn off the trace operation:
pushf
pop ax
and ah, 0feh ;Clear trace bit.
push ax
popf
; Disable trace operation.
pop es:[1*4 + 2] ;Restore previous INT 1
pop es:[1*4] ; handler.
; Okay, we're done. Restore registers and return.
VMDone: pop bx
pop ax
pop es
popf
ret
KbdSim endp
;----------------------------------------------------------------------------
;
; MyInt1- Handles the trace trap (INT 1). This code looks at the next
; opcode to determine if it is one of the special opcodes we have to
; handle ourselves.
MyInt1 proc far
push bp
mov bp, sp ;Gain access to return adrs via BP.
push bx
push ds
; If we get down here, it's because this trace trap is directly due to
; our having punched the trace bit. Let's process the trace trap to
; simulate the 80x86 instruction set.
;
; Get the return address into DS:BX
NextInstr: lds bx, 2[bp]
; The following is a special case to quickly eliminate most opcodes and
; speed up this code by a tiny amount.
cmp byp [bx], 0cdh ;Most opcodes are less than
jnb NotSimple ; 0cdh, hence we quickly
pop ds ; return back to the real
pop bx ; program.
pop bp
iret
NotSimple: je IsIntInstr ;If it's an INT instruction.
mov bx, [bx] ;Get current instruction's opcode.
cmp bl, 0e8h ;CALL opcode
je ExecInstr
jb TryInOut0
cmp bl, 0ech ;IN al, dx instr.
je MayBeIn60
cmp bl, 0eeh ;OUT dx, al instr.
je MayBeOut20
pop ds ;A normal instruction if we get
pop bx ; down here.
pop bp
iret
TryInOut0: cmp bx, 60e4h ;IN al, 60h instr.
je IsINAL60
cmp bx, 20e6h ;out 20, al instr.
je IsOut20
; If it wasn't one of our magic instructions, execute it and continue.
ExecInstr: pop ds
pop bx
pop bp
iret
; If this instruction is IN AL, DX we have to look at the value in DX to
; determine if it's really an IN AL, 60h instruction.
MayBeIn60: cmp dx, 60h
jne ExecInstr
inc word ptr 2[bp] ;Skip over this 1 byte instr.
mov al, cs:ScanCode
jmp NextInstr
; If this is an IN AL, 60h instruction, simulate it by loading the current
; scan code into AL.
IsInAL60: mov al, cs:ScanCode
add word ptr 2[bp], 2 ;Skip over this 2-byte instr.
jmp NextInstr
; If this instruction is OUT DX, AL we have to look at DX to see if we're
; outputting to location 20h (8259).
MayBeOut20: cmp dx, 20h
jne ExecInstr
inc word ptr 2[bp] ;Skip this 1 byte instruction.
jmp NextInstr
; If this is an OUT 20h, al instruction, simply skip over it.
IsOut20: add word ptr 2[bp], 2 ;Skip instruction.
jmp NextInstr
; IsIntInstr- Execute this code if it's an INT instruction.
;
; The problem with the INT instructions is that they reset the trace bit
; upon execution. For certain guys (see above) we can't have that.
;
; Note: at this point the stack looks like the following:
;
; flags
;
; rtn cs -+
; |
; rtn ip +-- Points at next instr the CPU will execute.
; bp
; bx
; ds
;
; We need to simulate the appropriate INT instruction by:
;
; (1) adding two to the return address on the stack (so it returns
; beyond the INT instruction.
; (2) pushing the flags onto the stack.
; (3) pushing a phony return address onto the stack which simulates
; the INT 1 interrupt return address but which "returns" us to
; the specified interrupt vector handler.
;
; All this results in a stack which looks like the following:
;
; flags
;
; rtn cs -+
; |
; rtn ip +-- Points at next instr beyond the INT instruction.
;
; flags --- Bogus flags to simulate those pushed by INT instr.
;
; rtn cs -+
; |
; rtn ip +-- "Return address" which points at the ISR for this INT.
; bp
; bx
; ds
IsINTInstr: add word ptr 2[bp], 2 ;Bump rtn adrs beyond INT instr.
mov bl, 1[bx]
mov bh, 0
shl bx, 1 ;Multiply by 4 to get vector
shl bx, 1 ; address.
push [bp-0] ;Get and save BP
push [bp-2] ;Get and save BX.
push [bp-4] ;Get and save DS.
push cx
xor cx, cx ;Point DS at interrupt
mov ds, cx ; vector table.
mov cx, [bp+6] ;Get original flags.
mov [bp-0], cx ;Save as pushed flags.
mov cx, ds:2[bx] ;Get vector and use it as
mov [bp-2], cx ; the return address.
mov cx, ds:[bx]
mov [bp-4], cx
pop cx
pop ds
pop bx
pop bp
iret
;
MyInt1 endp
; Main program - Simulates some keystrokes to demo the above code.
Main proc
mov ax, cseg
mov ds, ax
print
byte "Simulating keystrokes via Trace Flag",cr,lf
byte "This program places 'DIR' in the keyboard buffer"
byte cr,lf,0
mov al, 20h ;"D" down scan code
call KbdSim
mov al, 0a0h ;"D" up scan code
call KbdSim
mov al, 17h ;"I" down scan code
call KbdSim
mov al, 97h ;"I" up scan code
call KbdSim
mov al, 13h ;"R" down scan code
call KbdSim
mov al, 93h ;"R" up scan code
call KbdSim
mov al, 1Ch ;Enter down scan code
call KbdSim
mov al, 9Ch ;Enter up scan code
call KbdSim
ExitPgm
Main endp
cseg ends
sseg segment para stack 'stack'
stk byte 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg ends
end Main
- 20.7 - Simulating Keystrokes
- 20.7.1 - Stuffing Characters in the Type
Ahead Buffer
- 20.7.2 - Using the 80x86 Trace Flag to
Simulate IN AL, 60H Instructions
Art of Assembly: Chapter Twenty - 29 SEP 1996
[Chapter Twenty][Previous]
[Next] [Art of
Assembly][Randall Hyde]